use crate::constant::*;
use crossterm::event::{Event, KeyCode, MouseButton, MouseEventKind::*};
use log::info;
use std::any::Any;
use tge::{
    context::Context,
    event::{event_check, event_emit, timer_fire, timer_register},
    game::Model,
    util::{IPoint, Rand, Rect},
};

pub enum UnblockState {
    Normal,
    WinMoving,
    Win,
}

#[derive(Clone, Copy, Debug)]
pub struct Piece {
    pub x: f32,
    pub y: f32,
    pub kind: u8,
    pub start_cell_id: u8,
}

#[derive(Clone, Debug)]
pub struct Layout {
    pub moves: u16,
    pub pieces: Vec<Piece>,
}

pub struct UnblockModel {
    pub rand: Rand,
    pub map_index: u16,
    pub layout_origin: Layout,
    pub layout_run: Layout,
    pub main_piece: usize,
    pub selected_piece: Option<u8>,
    pub last_mouse_pos: IPoint,
    pub last_piece_pos: IPoint,
}

impl UnblockModel {
    pub fn new() -> Self {
        Self {
            rand: Rand::new(),
            map_index: 0,
            layout_origin: Layout {
                moves: 0,
                pieces: vec![],
            },
            layout_run: Layout {
                moves: 0,
                pieces: vec![],
            },
            main_piece: 0,
            selected_piece: None,
            last_mouse_pos: IPoint { x: 0, y: 0 },
            last_piece_pos: IPoint { x: 0, y: 0 },
        }
    }

    pub fn get_puzzle(&mut self, pid: u16) -> Layout {
        let mut lo = Layout {
            moves: 0,
            pieces: vec![],
        };
        let pz = PUZZLES[pid as usize];
        info!("...{:?}", pz);
        for p in pz {
            let x = (*p / 100) as u8;
            let y = (*p / 10 % 10) as u8;
            let kind = (*p % 10) as u8;
            let start = y * WIDTH + x;
            if kind == 1 {
                self.main_piece = start as usize;
            }
            lo.pieces.push(Piece {
                x: x as f32,
                y: y as f32,
                kind,
                start_cell_id: start,
            });
        }
        info!("...{:?}", lo.pieces);
        lo
    }

    pub fn reset(&mut self) {
        self.rand.srand_now();
        self.map_index = (self.rand.rand() % 8888) as u16;
        self.layout_origin = self.get_puzzle(self.map_index);
        self.layout_run = self.layout_origin.clone();
        self.selected_piece = None;
        timer_register("Unblock.WinMovie", 1.4, "_");
    }

    pub fn piece_to_rect(&self, p: &Piece) -> Rect {
        let adj = PADJ[p.kind as usize - 1];
        Rect {
            x: (p.x * CELLSIZEX as f32) as u16,
            y: (p.y * CELLSIZEY as f32) as u16,
            width: (adj[0] * CELLSIZEX) as u16,
            height: (adj[1] * CELLSIZEY) as u16,
        }
    }

    pub fn select_piece(&self, pt: IPoint) -> Option<u8> {
        let mut sp: Option<u8> = None;
        let mut i: u8 = 0;
        for p in &self.layout_run.pieces {
            let r = self.piece_to_rect(p);
            if pt.x <= r.x as i16 + r.width as i16
                && pt.x >= r.x as i16
                && pt.y <= r.y as i16 + r.height as i16
                && pt.y >= r.y as i16
            {
                sp = Some(i);
            }
            i += 1;
        }
        sp
    }

    pub fn move_piece(&self, pindex: u8, begin: IPoint, end: IPoint) -> Option<Piece> {
        let p = self.layout_run.pieces[pindex as usize];
        let mut mp = p.clone();
        let mut check_points: Vec<f32> = vec![];
        let adj = PADJ[mp.kind as usize - 1];
        let dr: bool = adj[0] > adj[1];
        let ov: f32 = if dr { mp.x as f32 } else { mp.y as f32 };
        let dv: f32 = if dr {
            (end.x as f32 - begin.x as f32) / CELLSIZEX as f32
        } else {
            (end.y as f32 - begin.y as f32) / CELLSIZEY as f32
        };
        if dv == 0.0 {
            return None;
        }
        let mut nv = ov + dv;
        if nv < 0.0 {
            nv = 0.0;
        }
        if nv > MAX[mp.kind as usize - 1] as f32 {
            nv = MAX[mp.kind as usize - 1] as f32;
        }
        let fh: f32 = if dv > 0.0 { 1.0 } else { -1.0 };
        let mut i = ov + fh;
        while fh * i < fh * nv {
            let bnv: i16;
            if dv > 0.0 {
                bnv = i as i16;
            } else {
                bnv = i as i16 + 1;
            }
            if bnv as f32 != ov && bnv as f32 != i {
                check_points.push(bnv as f32);
            }
            check_points.push(i);
            i = i + fh;
        }
        check_points.push(nv);
        info!("cps...{:?}", check_points);

        let mut ret: Option<f32> = None;
        for m in 0..check_points.len() {
            if dr {
                mp.x = check_points[m];
            } else {
                mp.y = check_points[m];
            }
            if !self.check_overlap(pindex, &mp) {
                if dr {
                    ret = Some(mp.x);
                } else {
                    ret = Some(mp.y);
                }
            } else {
                break;
            }
        }
        if ret == None {
            return None;
        }
        if dr {
            mp.x = ret.unwrap();
        } else {
            mp.y = ret.unwrap();
        }
        Some(mp)
    }

    pub fn homing_piece(&self, pindex: u8) -> Piece {
        let p = self.layout_run.pieces[pindex as usize];
        let mut mp = p.clone();
        let adj = PADJ[mp.kind as usize - 1];
        if adj[0] > adj[1] {
            mp.x = mp.x.round();
        } else {
            mp.y = mp.y.round();
        }
        mp
    }

    pub fn check_overlap(&self, pindex: u8, mp: &Piece) -> bool {
        let p = &self.layout_run.pieces;
        for i in 0..p.len() {
            if i == pindex as usize {
                continue;
            }
            let adj1 = PADJ[mp.kind as usize - 1];
            let adj2 = PADJ[p[i].kind as usize - 1];
            if mp.x + adj1[0] as f32 > p[i].x
                && p[i].x + adj2[0] as f32 > mp.x
                && mp.y + adj1[1] as f32 > p[i].y
                && p[i].y + adj2[1] as f32 > mp.y
            {
                return true;
            }
        }
        false
    }

    pub fn mouse_up(&mut self, context: &mut Context) {
        match self.selected_piece {
            Some(sp) => {
                let mp = self.homing_piece(sp);
                
                //skip invaild mouse_up...
                if mp.x as i16 == self.last_piece_pos.x && mp.y as i16 == self.last_piece_pos.y {
                    return;
                }

                self.layout_run.pieces[sp as usize] = mp;
                self.layout_run.moves += 1;
                self.last_piece_pos.x = mp.x as i16;
                self.last_piece_pos.y = mp.y as i16;
                info!("moves ++");
                event_emit("Unblock.RedrawGrid");
                // check success...
                if mp.kind == 1 && mp.x >= 4.0 {
                    context.state = UnblockState::WinMoving as u8;
                    timer_fire("Unblock.WinMovie", 0);
                }
            }
            _ => {}
        }
    }
}

impl Model for UnblockModel {
    fn init(&mut self, context: &mut Context) {
        context.input_events.clear();
        context.state = UnblockState::Normal as u8;
        event_emit("Unblock.Clear");
        self.reset();
    }

    fn handle_input(&mut self, context: &mut Context, _dt: f32) {
        let es = context.input_events.clone();
        for e in &es {
            match e {
                Event::Key(key) => match key.code {
                    KeyCode::Char('n') => {
                        self.init(context);
                    }
                    KeyCode::Char('r') => {
                        context.state = UnblockState::Normal as u8;
                        self.layout_run = self.layout_origin.clone();
                        event_emit("Unblock.Clear");
                    }
                    _ => {}
                },
                Event::Mouse(mou) => {
                    //info!("{:?}", mou);
                    let pc = IPoint {
                        x: mou.column as i16 - 1 - OFFX as i16,
                        y: mou.row as i16 - 1 - OFFY as i16,
                    };
                    if context.state != UnblockState::Normal as u8 {
                        break;
                    }
                    if mou.kind == Up(MouseButton::Left) {
                        self.mouse_up(context);
                    }
                    if mou.kind == Drag(MouseButton::Left) {
                        if self.selected_piece == None {
                            return;
                        }
                        let pindex = self.selected_piece.unwrap();
                        match self.move_piece(pindex, self.last_mouse_pos, pc) {
                            Some(mp) => {
                                self.layout_run.pieces[pindex as usize] = mp;
                                self.last_mouse_pos.x = pc.x;
                                self.last_mouse_pos.y = pc.y;
                                event_emit("Unblock.RedrawGrid");
                            }
                            _ => {}
                        }
                        //Drag out ...
                        if !(pc.x < UNBLOCKW as i16
                            && pc.x >= 0
                            && pc.y < UNBLOCKH as i16
                            && pc.y >= 0)
                        {
                            //Homing and check success...
                            info!("drag out...");
                            self.mouse_up(context);
                        }
                    }
                    if mou.kind == Down(MouseButton::Left) {
                        match self.select_piece(pc) {
                            Some(sp) => {
                                //Select piece...
                                self.selected_piece = Some(sp);
                                self.last_mouse_pos.x = pc.x;
                                self.last_mouse_pos.y = pc.y;
                                self.last_piece_pos.x = self.layout_run.pieces[sp as usize].x as i16;
                                self.last_piece_pos.y = self.layout_run.pieces[sp as usize].y as i16;
                            }
                            _ => {
                                self.selected_piece = None;
                            }
                        }
                    }
                }
                _ => {}
            }
        }
        context.input_events.clear();
    }

    fn handle_auto(&mut self, _context: &mut Context, _dt: f32) {}
    fn handle_event(&mut self, _context: &mut Context, _dt: f32) {}
    fn handle_timer(&mut self, context: &mut Context, _dt: f32) {
        if event_check("Unblock.WinMovie", "_") {
            context.state = UnblockState::Win as u8;
        }
    }

    fn as_any(&mut self) -> &mut dyn Any {
        self
    }
}
